package eva

import (
	"fmt"
	"math"

	"gitcode.com/deyiyangyang/gascheme/ast"
	"gitcode.com/deyiyangyang/gascheme/obj"
)

// Helper function: calculate greatest common divisor
func Gcd(a, b int64) int64 {
	for b != 0 {
		a, b = b, a%b
	}
	return a
}

// Helper function: calculate least common multiple
func Lcm(a, b int64) int64 {
	if a == 0 || b == 0 {
		return 0
	}
	return (a * b) / Gcd(a, b)
}

// Helper function: calculate absolute value
func Abs(a int64) int64 {
	if a < 0 {
		return -a
	}
	return a
}

// Define function - defines variables and procedures in the environment
func Define(args []obj.Object, env obj.Environment) obj.Object {
	// Check if we have at least 2 arguments
	if len(args) < 2 {
		return &obj.Error{Message: "define requires at least 2 arguments"}
	}

	// Handle(define name value) form - first argument is a symbol
	if nameObj, ok := args[0].(*obj.Symbol); ok {
		value := args[1]
		// In the environment, set the variable
		env.Set(nameObj.Value, value)
		return value
	}

	// Handle(define (name arg1 arg2 ...) body) form - first argument is a DefineProcedure object
	if defineProcObj, ok := args[0].(*obj.DefineProcedure); ok {
		if defineProcObj.Signature == nil || defineProcObj.Signature.Length() < 1 {
			return &obj.Error{Message: "invalid procedure definition in define"}
		}

		// Convert signature to slice for easier access
		signatureElements := defineProcObj.Signature.ToSlice()
		
		// Check if the procedure name is a symbol
		procNameObj, ok := signatureElements[0].(*obj.Symbol)
		if !ok {
			return &obj.Error{Message: "procedure name must be a symbol"}
		}

		// Extract parameters
		var params []string
		for i := 1; i < len(signatureElements); i++ {
			paramObj, ok := signatureElements[i].(*obj.Symbol)
			if !ok {
				return &obj.Error{Message: "procedure parameters must be symbols"}
			}
			params = append(params, paramObj.Value)
		}

		// Create a procedure object
		proc := &obj.Procedure{
			Name:   procNameObj.Value,
			Params: params,
			Body:   defineProcObj.Body,
			Env:    env,
		}

		// In the environment, set the procedure
		env.Set(procNameObj.Value, proc)
		return proc
	}

	return &obj.Error{Message: "define expects a symbol or a procedure definition as the first argument"}
}

// Addition function - handles multiple arguments according to R4RS standard
func Add(args []obj.Object, env obj.Environment) obj.Object {
	// Handle zero arguments - returns 0
	if len(args) == 0 {
		return &obj.Integer{Value: 0}
	}

	// Check if any argument is real
	hasReal := false
	for _, arg := range args {
		if arg.Type() == obj.REAL_OBJ {
			hasReal = true
			break
		}
	}

	// Check if any argument is rational but not integer
	hasRational := false
	for _, arg := range args {
		if arg.Type() == obj.RATIONAL_OBJ {
			hasRational = true
			break
		}
	}

	if hasReal {
		// If there's a real, result is real
		var result float64 = 0
		for _, arg := range args {
			switch arg := arg.(type) {
			case *obj.Integer:
				result += float64(arg.Value)
			case *obj.Rational:
				result += float64(arg.Numerator) / float64(arg.Denominator)
			case *obj.Real:
				result += arg.Value
			default:
				return &obj.Error{Message: fmt.Sprintf("+ expects number arguments, got %s", arg.Type())}
			}
		}
		return &obj.Real{Value: result}
	} else if hasRational {
		// If there are rationals but no reals, result is rational
		// Start with first argument
		var resultNumerator, resultDenominator int64

		switch firstArg := args[0].(type) {
		case *obj.Integer:
			resultNumerator = firstArg.Value
			resultDenominator = 1
		case *obj.Rational:
			resultNumerator = firstArg.Numerator
			resultDenominator = firstArg.Denominator
		default:
			return &obj.Error{Message: fmt.Sprintf("+ expects number arguments, got %s", args[0].Type())}
		}

		// Add remaining arguments
		for _, arg := range args[1:] {
			switch arg := arg.(type) {
			case *obj.Integer:
				// Convert integer to rational and add
				argNumerator := arg.Value
				argDenominator := int64(1)

				// Find common denominator
				lcmVal := Lcm(resultDenominator, argDenominator)

				// Convert to common denominator
				resultNumerator = resultNumerator*(lcmVal/resultDenominator) + argNumerator*(lcmVal/1)
				resultDenominator = lcmVal
			case *obj.Rational:
				// Find common denominator
				lcmVal := Lcm(resultDenominator, arg.Denominator)

				// Convert to common denominator
				resultNumerator = resultNumerator*(lcmVal/resultDenominator) + arg.Numerator*(lcmVal/arg.Denominator)
				resultDenominator = lcmVal
			default:
				return &obj.Error{Message: fmt.Sprintf("+ expects number arguments, got %s", arg.Type())}
			}

			// Simplify the fraction
			gcdVal := Gcd(resultNumerator, resultDenominator)
			resultNumerator /= gcdVal
			resultDenominator /= gcdVal
		}

		return &obj.Rational{Numerator: resultNumerator, Denominator: resultDenominator}
	} else {
		// All integers, result is integer
		var result int64 = 0
		for _, arg := range args {
			switch arg := arg.(type) {
			case *obj.Integer:
				result += arg.Value
			default:
				return &obj.Error{Message: fmt.Sprintf("+ expects number arguments, got %s", arg.Type())}
			}
		}
		return &obj.Integer{Value: result}
	}
}

// Subtraction function - handles multiple arguments according to R4RS standard
func Subtract(args []obj.Object, env obj.Environment) obj.Object {
	// Handle one argument - returns negative of the argument
	if len(args) == 1 {
		switch arg := args[0].(type) {
		case *obj.Integer:
			return &obj.Integer{Value: -arg.Value}
		case *obj.Rational:
			return &obj.Rational{Numerator: -arg.Numerator, Denominator: arg.Denominator}
		case *obj.Real:
			return &obj.Real{Value: -arg.Value}
		default:
			return &obj.Error{Message: fmt.Sprintf("- expects number arguments, got %s", arg.Type())}
		}
	}

	// Check if any argument is real
	hasReal := false
	for _, arg := range args {
		if arg.Type() == obj.REAL_OBJ {
			hasReal = true
			break
		}
	}

	// Check if any argument is rational but not integer
	hasRational := false
	for _, arg := range args {
		if arg.Type() == obj.RATIONAL_OBJ {
			hasRational = true
			break
		}
	}

	if hasReal {
		// If there's a real, result is real
		// Start with first argument
		var result float64
		switch firstArg := args[0].(type) {
		case *obj.Integer:
			result = float64(firstArg.Value)
		case *obj.Rational:
			result = float64(firstArg.Numerator) / float64(firstArg.Denominator)
		case *obj.Real:
			result = firstArg.Value
		default:
			return &obj.Error{Message: fmt.Sprintf("- expects number arguments, got %s", args[0].Type())}
		}

		// Subtract remaining arguments
		for _, arg := range args[1:] {
			switch arg := arg.(type) {
			case *obj.Integer:
				result -= float64(arg.Value)
			case *obj.Rational:
				result -= float64(arg.Numerator) / float64(arg.Denominator)
			case *obj.Real:
				result -= arg.Value
			default:
				return &obj.Error{Message: fmt.Sprintf("- expects number arguments, got %s", arg.Type())}
			}
		}

		return &obj.Real{Value: result}
	} else if hasRational {
		// If there are rationals but no reals, result is rational
		// Start with first argument
		var resultNumerator, resultDenominator int64
		switch firstArg := args[0].(type) {
		case *obj.Integer:
			resultNumerator = firstArg.Value
			resultDenominator = 1
		case *obj.Rational:
			resultNumerator = firstArg.Numerator
			resultDenominator = firstArg.Denominator
		default:
			return &obj.Error{Message: fmt.Sprintf("- expects number arguments, got %s", args[0].Type())}
		}

		// Subtract remaining arguments
		for _, arg := range args[1:] {
			switch arg := arg.(type) {
			case *obj.Integer:
				// Convert integer to rational and subtract
				argNumerator := arg.Value
				argDenominator := int64(1)

				// Find common denominator
				lcmVal := Lcm(resultDenominator, argDenominator)

				// Convert to common denominator and subtract
				resultNumerator = resultNumerator*(lcmVal/resultDenominator) - argNumerator*(lcmVal/1)
				resultDenominator = lcmVal
			case *obj.Rational:
				// Find common denominator
				lcmVal := Lcm(resultDenominator, arg.Denominator)

				// Convert to common denominator and subtract
				resultNumerator = resultNumerator*(lcmVal/resultDenominator) - arg.Numerator*(lcmVal/arg.Denominator)
				resultDenominator = lcmVal
			default:
				return &obj.Error{Message: fmt.Sprintf("- expects number arguments, got %s", arg.Type())}
			}

			// Simplify the fraction
			gcdVal := Gcd(Abs(resultNumerator), resultDenominator)
			resultNumerator /= gcdVal
			resultDenominator /= gcdVal
		}

		return &obj.Rational{Numerator: resultNumerator, Denominator: resultDenominator}
	} else {
		// All integers, result is integer
		firstArg, ok := args[0].(*obj.Integer)
		if !ok {
			return &obj.Error{Message: fmt.Sprintf("- expects integer arguments, got %s", args[0].Type())}
		}

		result := firstArg.Value
		for _, arg := range args[1:] {
			switch arg := arg.(type) {
			case *obj.Integer:
				result -= arg.Value
			default:
				return &obj.Error{Message: fmt.Sprintf("- expects integer arguments, got %s", arg.Type())}
			}
		}
		return &obj.Integer{Value: result}
	}
}

// Multiplication function - handles multiple arguments according to R4RS standard
func Multiply(args []obj.Object, env obj.Environment) obj.Object {
	// Handle zero arguments - returns 1
	if len(args) == 0 {
		return &obj.Integer{Value: 1}
	}

	// Check if any argument is real
	hasReal := false
	for _, arg := range args {
		if arg.Type() == obj.REAL_OBJ {
			hasReal = true
			break
		}
	}

	// Check if any argument is rational but not integer
	hasRational := false
	for _, arg := range args {
		if arg.Type() == obj.RATIONAL_OBJ {
			hasRational = true
			break
		}
	}

	if hasReal {
		// If there's a real, result is real
		var result float64 = 1
		for _, arg := range args {
			switch arg := arg.(type) {
			case *obj.Integer:
				result *= float64(arg.Value)
			case *obj.Rational:
				result *= float64(arg.Numerator) / float64(arg.Denominator)
			case *obj.Real:
				result *= arg.Value
			default:
				return &obj.Error{Message: fmt.Sprintf("* expects number arguments, got %s", arg.Type())}
			}
		}
		return &obj.Real{Value: result}
	} else if hasRational {
		// If there are rationals but no reals, result is rational
		resultNumerator := int64(1)
		resultDenominator := int64(1)

		for _, arg := range args {
			switch arg := arg.(type) {
			case *obj.Integer:
				resultNumerator *= arg.Value
			case *obj.Rational:
				resultNumerator *= arg.Numerator
				resultDenominator *= arg.Denominator
			default:
				return &obj.Error{Message: fmt.Sprintf("* expects number arguments, got %s", arg.Type())}
			}

			// Simplify fraction at each step to avoid overflow
			gcdVal := Gcd(Abs(resultNumerator), resultDenominator)
			resultNumerator /= gcdVal
			resultDenominator /= gcdVal
		}

		return &obj.Rational{Numerator: resultNumerator, Denominator: resultDenominator}
	} else {
		// All integers, result is integer
		var result int64 = 1
		for _, arg := range args {
			switch arg := arg.(type) {
			case *obj.Integer:
				result *= arg.Value
			default:
				return &obj.Error{Message: fmt.Sprintf("* expects number arguments, got %s", arg.Type())}
			}
		}
		return &obj.Integer{Value: result}
	}
}

// Division function - handles multiple arguments according to R4RS standard
func Divide(args []obj.Object, env obj.Environment) obj.Object {
	// Handle zero arguments - error
	if len(args) == 0 {
		return &obj.Error{Message: "/ requires at least 1 argument"}
	}

	// Handle one argument - returns reciprocal
	if len(args) == 1 {
		switch arg := args[0].(type) {
		case *obj.Integer:
			if arg.Value == 0 {
				return &obj.Error{Message: "/ division by zero"}
			}
			return &obj.Rational{Numerator: 1, Denominator: arg.Value}
		case *obj.Rational:
			if arg.Numerator == 0 {
				return &obj.Error{Message: "/ division by zero"}
			}
			// Reciprocal of a rational is denominator/numerator
			return &obj.Rational{Numerator: arg.Denominator, Denominator: arg.Numerator}
		case *obj.Real:
			if math.Abs(arg.Value) < 1e-10 {
				return &obj.Error{Message: "/ division by zero"}
			}
			return &obj.Real{Value: 1.0 / arg.Value}
		default:
			return &obj.Error{Message: fmt.Sprintf("/ expects number arguments, got %s", arg.Type())}
		}
	}

	// Check if any argument is real
	hasReal := false
	for _, arg := range args {
		if arg.Type() == obj.REAL_OBJ {
			hasReal = true
			break
		}
	}

	// Check if any argument is rational but not integer
	hasRational := false
	for _, arg := range args {
		if arg.Type() == obj.RATIONAL_OBJ {
			hasRational = true
			break
		}
	}

	// Check divisors for zero
	for i := 1; i < len(args); i++ {
		switch arg := args[i].(type) {
		case *obj.Integer:
			if arg.Value == 0 {
				return &obj.Error{Message: "/ division by zero"}
			}
		case *obj.Rational:
			if arg.Numerator == 0 {
				return &obj.Error{Message: "/ division by zero"}
			}
		case *obj.Real:
			if math.Abs(arg.Value) < 1e-10 {
				return &obj.Error{Message: "/ division by zero"}
			}
		}
	}

	if hasReal {
		// If there's a real, result is real
		// Start with first argument
		var result float64
		switch firstArg := args[0].(type) {
		case *obj.Integer:
			result = float64(firstArg.Value)
		case *obj.Rational:
			result = float64(firstArg.Numerator) / float64(firstArg.Denominator)
		case *obj.Real:
			result = firstArg.Value
		default:
			return &obj.Error{Message: fmt.Sprintf("/ expects number arguments, got %s", args[0].Type())}
		}

		// Divide by remaining arguments
		for _, arg := range args[1:] {
			switch arg := arg.(type) {
			case *obj.Integer:
				result /= float64(arg.Value)
			case *obj.Rational:
				// Dividing by a rational is multiplying by its reciprocal
				result *= float64(arg.Denominator) / float64(arg.Numerator)
			case *obj.Real:
				result /= arg.Value
			default:
				return &obj.Error{Message: fmt.Sprintf("/ expects number arguments, got %s", arg.Type())}
			}
		}

		return &obj.Real{Value: result}
	} else if hasRational {
		// If there are rationals but no reals, result is rational
		// Start with first argument
		var resultNumerator, resultDenominator int64
		switch firstArg := args[0].(type) {
		case *obj.Integer:
			resultNumerator = firstArg.Value
			resultDenominator = 1
		case *obj.Rational:
			resultNumerator = firstArg.Numerator
			resultDenominator = firstArg.Denominator
		default:
			return &obj.Error{Message: fmt.Sprintf("/ expects number arguments, got %s", args[0].Type())}
		}

		// Divide by remaining arguments
		for _, arg := range args[1:] {
			switch arg := arg.(type) {
			case *obj.Integer:
				// Dividing by an integer is multiplying by its reciprocal
				resultNumerator *= 1
				resultDenominator *= arg.Value
			case *obj.Rational:
				// Dividing by a rational is multiplying by its reciprocal
				resultNumerator *= arg.Denominator
				resultDenominator *= arg.Numerator
			default:
				return &obj.Error{Message: fmt.Sprintf("/ expects number arguments, got %s", arg.Type())}
			}

			// Simplify the fraction
			gcdVal := Gcd(Abs(resultNumerator), resultDenominator)
			resultNumerator /= gcdVal
			resultDenominator /= gcdVal

			// Ensure denominator is positive
			if resultDenominator < 0 {
				resultNumerator = -resultNumerator
				resultDenominator = -resultDenominator
			}
		}

		return &obj.Rational{Numerator: resultNumerator, Denominator: resultDenominator}
	} else {
		// All integers, result could be integer or rational
		firstArg, ok := args[0].(*obj.Integer)
		if !ok {
			return &obj.Error{Message: fmt.Sprintf("/ expects integer arguments, got %s", args[0].Type())}
		}

		resultNumerator := firstArg.Value
		resultDenominator := int64(1)

		for _, arg := range args[1:] {
			switch arg := arg.(type) {
			case *obj.Integer:
				// Update numerator and denominator
				resultDenominator *= arg.Value

				// Simplify fraction
				gcdVal := Gcd(Abs(resultNumerator), resultDenominator)
				resultNumerator /= gcdVal
				resultDenominator /= gcdVal
			default:
				return &obj.Error{Message: fmt.Sprintf("/ expects integer arguments, got %s", arg.Type())}
			}
		}

		// If denominator is 1, return integer
		if resultDenominator == 1 {
			return &obj.Integer{Value: resultNumerator}
		}

		// Otherwise return rational
		return &obj.Rational{Numerator: resultNumerator, Denominator: resultDenominator}
	}
}

// Boolean NOT function
func Not(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 1 {
		return &obj.Error{Message: "not requires exactly 1 argument"}
	}

	arg := args[0]
	// In Scheme, anything that is not #f is considered true
	if arg.Type() == obj.BOOLEAN_OBJ {
		if arg.(*obj.Boolean).Value == false {
			return &obj.Boolean{Value: true}
		}
	}
	return &obj.Boolean{Value: false}
}

// Boolean AND function
func And(args []obj.Object, env obj.Environment) obj.Object {
	// In Scheme, and with no arguments returns #t
	if len(args) == 0 {
		return &obj.Boolean{Value: true}
	}

	// Evaluate each argument and return the first #f
	for i, arg := range args {
		// In Scheme, the evaluation of and is short-circuited
		// For built-in procedures, we don't have access to the evaluator here
		// So we directly check if the argument is #f
		if arg.Type() == obj.BOOLEAN_OBJ && !arg.(*obj.Boolean).Value {
			return &obj.Boolean{Value: false}
		}

		// Return the last argument if all are true
		if i == len(args)-1 {
			return arg
		}
	}

	// This should never be reached
	return &obj.Boolean{Value: true}
}

// Boolean OR function
func Or(args []obj.Object, env obj.Environment) obj.Object {
	// In Scheme, or with no arguments returns #f
	if len(args) == 0 {
		return &obj.Boolean{Value: false}
	}

	// Evaluate each argument and return the first non-#f
	for _, arg := range args {
		// In Scheme, the evaluation of or is short-circuited
		// For built-in procedures, we don't have access to the evaluator here
		// So we directly check if the argument is #f
		if !(arg.Type() == obj.BOOLEAN_OBJ && !arg.(*obj.Boolean).Value) {
			return arg
		}
	}

	// All arguments evaluated to #f
	return &obj.Boolean{Value: false}
}

// eq? function - strict equality test for symbols, booleans, and numbers
// Returns #t if the arguments are the same object, #f otherwise
func Eq(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 2 {
		return &obj.Error{Message: "eq? requires exactly 2 arguments"}
	}

	a, b := args[0], args[1]

	// For symbols, eq? checks if they are the same symbol object
	// In our implementation, we compare their string values directly
	aSym, aIsSym := a.(*obj.Symbol)
	bSym, bIsSym := b.(*obj.Symbol)
	if aIsSym && bIsSym {
		return &obj.Boolean{Value: aSym.Value == bSym.Value}
	}

	// For booleans, eq? checks if they have the same value
	aBool, aIsBool := a.(*obj.Boolean)
	bBool, bIsBool := b.(*obj.Boolean)
	if aIsBool && bIsBool {
		return &obj.Boolean{Value: aBool.Value == bBool.Value}
	}

	// For numbers, eq? behaves like eqv?
	aNumType := a.Type()
	bNumType := b.Type()
	if (aNumType == obj.INTEGER_OBJ || aNumType == obj.RATIONAL_OBJ || aNumType == obj.REAL_OBJ) &&
		(bNumType == obj.INTEGER_OBJ || bNumType == obj.RATIONAL_OBJ || bNumType == obj.REAL_OBJ) {
		// Delegate to Eqv for numeric equality
		return Eqv(args, env)
	}

	// For other types, eq? returns #f
	return &obj.Boolean{Value: false}
}

// eqv? function - equality test that treats certain types specially
// For numbers, it checks if they are numerically equal
// For symbols and booleans, it behaves like eq?
func Eqv(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 2 {
		return &obj.Error{Message: "eqv? requires exactly 2 arguments"}
	}

	a, b := args[0], args[1]

	// If objects are of different types, they are not eqv
	if a.Type() != b.Type() {
		return &obj.Boolean{Value: false}
	}

	// Handle different types
	switch a.Type() {
	case obj.INTEGER_OBJ:
		return &obj.Boolean{Value: a.(*obj.Integer).Value == b.(*obj.Integer).Value}
	case obj.RATIONAL_OBJ:
		aRat, bRat := a.(*obj.Rational), b.(*obj.Rational)
		return &obj.Boolean{Value: aRat.Numerator == bRat.Numerator && aRat.Denominator == bRat.Denominator}
	case obj.REAL_OBJ:
		return &obj.Boolean{Value: math.Abs(a.(*obj.Real).Value-b.(*obj.Real).Value) < 1e-10}
	case obj.BOOLEAN_OBJ:
		return &obj.Boolean{Value: a.(*obj.Boolean).Value == b.(*obj.Boolean).Value}
	case obj.SYMBOL_OBJ:
		return &obj.Boolean{Value: a.(*obj.Symbol).Value == b.(*obj.Symbol).Value}
	default:
		// For other types, eqv? behaves like eq?
		return Eq(args, env)
	}
}

// equal? function - general equality test
// For most types, it behaves like eqv?
// For lists, it recursively checks if all elements are equal
func Equal(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 2 {
		return &obj.Error{Message: "equal? requires exactly 2 arguments"}
	}

	a, b := args[0], args[1]

	// If objects are of different types, they are not equal
	if a.Type() != b.Type() {
		return &obj.Boolean{Value: false}
	}

	// For lists, recursively check each element
	if a.Type() == obj.LIST_OBJ {
		aList, bList := a.(*obj.List), b.(*obj.List)
		aElements := aList.ToSlice()
		bElements := bList.ToSlice()
		
		if len(aElements) != len(bElements) {
			return &obj.Boolean{Value: false}
		}

		for i := range aElements {
			equalResult := Equal([]obj.Object{aElements[i], bElements[i]}, env)
			if errObj, ok := equalResult.(*obj.Error); ok {
				return errObj
			}
			if !equalResult.(*obj.Boolean).Value {
				return &obj.Boolean{Value: false}
			}
		}
		return &obj.Boolean{Value: true}
	}

	// For other types, equal? behaves like eqv?
	return Eqv(args, env)
}

// Quote function - returns the quoted (unevaluated) expression
func Quote(args []obj.Object, env obj.Environment) obj.Object {
	// Quote requires exactly 1 argument
	if len(args) != 1 {
		return &obj.Error{Message: "quote requires exactly 1 argument"}
	}

	// Simply return the argument as-is since it's already been converted from AST to object
	return args[0]
}

// LessThan function - checks if the first argument is less than the second
func LessThan(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 2 {
		return &obj.Error{Message: "< requires exactly 2 arguments"}
	}

	// Convert both arguments to float64 for comparison
	aFloat := getFloatValue(args[0])
	bFloat := getFloatValue(args[1])

	if aFloat == nil || bFloat == nil {
		return &obj.Error{Message: "< expects number arguments"}
	}

	return &obj.Boolean{Value: *aFloat < *bFloat}
}

// GreaterThan function - checks if the first argument is greater than the second
func GreaterThan(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 2 {
		return &obj.Error{Message: "> requires exactly 2 arguments"}
	}

	// Convert both arguments to float64 for comparison
	aFloat := getFloatValue(args[0])
	bFloat := getFloatValue(args[1])

	if aFloat == nil || bFloat == nil {
		return &obj.Error{Message: "> expects number arguments"}
	}

	return &obj.Boolean{Value: *aFloat > *bFloat}
}

// LessThanEqual function - checks if the first argument is less than or equal to the second
func LessThanEqual(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 2 {
		return &obj.Error{Message: "<= requires exactly 2 arguments"}
	}

	// Convert both arguments to float64 for comparison
	aFloat := getFloatValue(args[0])
	bFloat := getFloatValue(args[1])

	if aFloat == nil || bFloat == nil {
		return &obj.Error{Message: "<= expects number arguments"}
	}

	return &obj.Boolean{Value: *aFloat <= *bFloat}
}

// GreaterThanEqual function - checks if the first argument is greater than or equal to the second
func GreaterThanEqual(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 2 {
		return &obj.Error{Message: ">= requires exactly 2 arguments"}
	}

	// Convert both arguments to float64 for comparison
	aFloat := getFloatValue(args[0])
	bFloat := getFloatValue(args[1])

	if aFloat == nil || bFloat == nil {
		return &obj.Error{Message: ">= expects number arguments"}
	}

	return &obj.Boolean{Value: *aFloat >= *bFloat}
}

// Helper function to convert any number type to float64
func getFloatValue(obj obj.Object) *float64 {
	objType := obj.Type()

	// 使用类型断言，但不引用obj包中的具体类型
	switch objType {
	case "INTEGER":
		// 尝试将对象转换为具有Value字段的类型
		if i, ok := getIntegerValue(obj); ok {
			val := float64(i)
			return &val
		}
	case "RATIONAL":
		// 尝试获取分子和分母
		if numerator, denominator, ok := getRationalValues(obj); ok {
			val := float64(numerator) / float64(denominator)
			return &val
		}
	case "REAL":
		// 尝试将对象转换为具有Value字段的类型
		if r, ok := getRealValue(obj); ok {
			return &r
		}
	}

	return nil
}

// 辅助函数，尝试从对象中获取整数值
func getIntegerValue(obj obj.Object) (int64, bool) {
	// 使用反射方式获取字段值
	// 这里简化处理，假设对象有正确的结构
	inspectStr := obj.Inspect()

	// 尝试将inspect返回的字符串解析为整数
	// 这是一个简化的实现
	var value int64
	_, err := fmt.Sscanf(inspectStr, "%d", &value)
	if err == nil {
		return value, true
	}

	return 0, false
}

// 辅助函数，尝试从对象中获取有理数值
func getRationalValues(obj obj.Object) (int64, int64, bool) {
	// 使用反射方式获取字段值
	// 这里简化处理，假设对象有正确的结构
	inspectStr := obj.Inspect()

	// 尝试将inspect返回的字符串解析为分子和分母
	var numerator, denominator int64
	_, err := fmt.Sscanf(inspectStr, "%d/%d", &numerator, &denominator)
	if err == nil && denominator != 0 {
		return numerator, denominator, true
	}

	return 0, 0, false
}

// 辅助函数，尝试从对象中获取实数值
func getRealValue(obj obj.Object) (float64, bool) {
	// 使用反射方式获取字段值
	// 这里简化处理，假设对象有正确的结构
	inspectStr := obj.Inspect()

	// 尝试将inspect返回的字符串解析为浮点数
	var value float64
	_, err := fmt.Sscanf(inspectStr, "%f", &value)
	if err == nil {
		return value, true
	}

	return 0, false
}

// Lambda function - creates an anonymous procedure
// This function is a placeholder and should never be called directly
// The actual lambda implementation is handled in the evaluator
func Lambda(args []obj.Object, env obj.Environment) obj.Object {
	return &obj.Error{Message: "lambda should be handled directly by the evaluator"}
}

// Helper function to convert objects back to expressions
// This is a simplified version - in reality, we would need to reconstruct the AST
func convertObjectsToExpressions(objects []obj.Object) []ast.Expression {
	// For now, we'll return an empty slice
	// This is a placeholder - we'll need to properly implement this
	// when we modify how lambda expressions are handled
	return []ast.Expression{}
}

// RegisterBuiltinProcedures registers all built-in procedures in the global environment
func RegisterBuiltinProcedures(globalEnv *obj.GlobalEnvironment) {
	// Register arithmetic operations
	globalEnv.Set("+", &obj.Builtin{Fn: Add})
	globalEnv.Set("-", &obj.Builtin{Fn: Subtract})
	globalEnv.Set("*", &obj.Builtin{Fn: Multiply})
	globalEnv.Set("/", &obj.Builtin{Fn: Divide})
	globalEnv.Set("set!", &obj.Builtin{Fn: Set})
	globalEnv.Set("define", &obj.Builtin{Fn: Define})
	globalEnv.Set("quote", &obj.Builtin{Fn: Quote})
	globalEnv.Set("lambda", &obj.Builtin{Fn: Lambda})
	globalEnv.Set("apply", &obj.Builtin{Fn: Apply})
	globalEnv.Set("map", &obj.Builtin{Fn: Map})

	// Register comparison operations
	globalEnv.Set("=", &obj.Builtin{Fn: Equal})
	globalEnv.Set("<", &obj.Builtin{Fn: LessThan})
	globalEnv.Set(">", &obj.Builtin{Fn: GreaterThan})
	globalEnv.Set("<=", &obj.Builtin{Fn: LessThanEqual})
	globalEnv.Set(">=", &obj.Builtin{Fn: GreaterThanEqual})

	// Register logical operations
	globalEnv.Set("not", &obj.Builtin{Fn: Not})
	globalEnv.Set("and", &obj.Builtin{Fn: And})
	globalEnv.Set("or", &obj.Builtin{Fn: Or})

	// Register equality operations
	globalEnv.Set("eq?", &obj.Builtin{Fn: Eq})
	globalEnv.Set("eqv?", &obj.Builtin{Fn: Eqv})
	globalEnv.Set("equal?", &obj.Builtin{Fn: Equal})

	// Register character operations
	globalEnv.Set("char?", &obj.Builtin{Fn: Char})
	globalEnv.Set("char=?", &obj.Builtin{Fn: CharEqual})
	globalEnv.Set("char<?", &obj.Builtin{Fn: CharLess})
	globalEnv.Set("char>?", &obj.Builtin{Fn: CharGreater})
	globalEnv.Set("char<=?", &obj.Builtin{Fn: CharLessEqual})
	globalEnv.Set("char>=?", &obj.Builtin{Fn: CharGreaterEqual})

	// Register list operations
	RegisterListProcedures(globalEnv)
	
	// Register mutation operations
	globalEnv.Set("set-car!", &obj.Builtin{Fn: SetCar})
	globalEnv.Set("set-cdr!", &obj.Builtin{Fn: SetCdr})
	
	// Register mathematical functions
	globalEnv.Set("sqrt", &obj.Builtin{Fn: Sqrt})
	globalEnv.Set("expt", &obj.Builtin{Fn: Expt})
}

// Set! function - modifies the value of a variable in the environment
func Set(args []obj.Object, env obj.Environment) obj.Object {
	// Check if we have exactly 2 arguments
	if len(args) != 2 {
		return &obj.Error{Message: "set! requires exactly 2 arguments"}
	}

	// First argument must be a symbol - when called as builtin, it will be a symbol object
	// not a symbol literal AST node
	symbol, ok := args[0].(*obj.Symbol)
	if !ok {
		return &obj.Error{Message: "set! first argument must be a symbol"}
	}

	// Get the value argument (already evaluated)
	value := args[1]

	// Check if the variable exists in the environment
	if _, exists := env.Get(symbol.Value); !exists {
		return &obj.Error{Message: fmt.Sprintf("set!: undefined variable: %s", symbol.Value)}
	}

	// Set the value in the environment
	env.Set(symbol.Value, value)

	// In Scheme, set! returns undefined behavior
	// We'll return the value that was set
	return value
}

// Sqrt function - calculates the square root of a number
func Sqrt(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 1 {
		return &obj.Error{Message: "sqrt requires exactly 1 argument"}
	}

	arg := args[0]
	switch arg := arg.(type) {
	case *obj.Integer:
		if arg.Value < 0 {
			// For negative integers, return a complex number (represented as real)
			result := math.Sqrt(float64(-arg.Value))
			return &obj.Real{Value: result}
		}
		// For non-negative integers, check if result is integer
		sqrtVal := math.Sqrt(float64(arg.Value))
		if sqrtVal == float64(int64(sqrtVal)) {
			return &obj.Integer{Value: int64(sqrtVal)}
		}
		return &obj.Real{Value: sqrtVal}
	case *obj.Rational:
		if arg.Numerator < 0 {
			// For negative rationals, compute sqrt of positive part and return as real
			val := math.Sqrt(float64(-arg.Numerator)/float64(arg.Denominator))
			return &obj.Real{Value: val}
		}
		// For non-negative rationals, compute as real
		val := math.Sqrt(float64(arg.Numerator) / float64(arg.Denominator))
		return &obj.Real{Value: val}
	case *obj.Real:
		if arg.Value < 0 {
			// For negative reals, compute as positive and return as real
			val := math.Sqrt(-arg.Value)
			return &obj.Real{Value: val}
		}
		return &obj.Real{Value: math.Sqrt(arg.Value)}
	default:
		return &obj.Error{Message: fmt.Sprintf("sqrt expects a number, got %s", arg.Type())}
	}
}

// Expt function - raises a number to the power of another number
func Expt(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 2 {
		return &obj.Error{Message: "expt requires exactly 2 arguments"}
	}

	base := args[0]
	exponent := args[1]

	// Handle different combinations of base and exponent types
	switch base := base.(type) {
	case *obj.Integer:
		switch exp := exponent.(type) {
		case *obj.Integer:
			// Integer to integer power
			if exp.Value == 0 {
				return &obj.Integer{Value: 1}
			}
			if exp.Value > 0 {
				result := int64(1)
				for i := int64(0); i < exp.Value; i++ {
					result *= base.Value
				}
				return &obj.Integer{Value: result}
			}
			// Negative exponent - return as rational
			denom := int64(1)
			for i := int64(0); i < -exp.Value; i++ {
				denom *= base.Value
			}
			gcd := Gcd(1, denom)
			return &obj.Rational{Numerator: 1 / gcd, Denominator: denom / gcd}
		case *obj.Rational:
			// Integer to rational power - convert to real
			baseVal := float64(base.Value)
			expVal := float64(exp.Numerator) / float64(exp.Denominator)
			return &obj.Real{Value: math.Pow(baseVal, expVal)}
		case *obj.Real:
			// Integer to real power
			return &obj.Real{Value: math.Pow(float64(base.Value), exp.Value)}
		default:
			return &obj.Error{Message: fmt.Sprintf("expt expects number as second argument, got %s", exponent.Type())}
		}
	case *obj.Rational:
		switch exp := exponent.(type) {
		case *obj.Integer:
			// Rational to integer power
			if exp.Value == 0 {
				return &obj.Integer{Value: 1}
			}
			if exp.Value > 0 {
				num := int64(1)
				denom := int64(1)
				for i := int64(0); i < exp.Value; i++ {
					num *= base.Numerator
					denom *= base.Denominator
				}
				gcd := Gcd(num, denom)
				return &obj.Rational{Numerator: num / gcd, Denominator: denom / gcd}
			}
			// Negative exponent
			num := int64(1)
			denom := int64(1)
			for i := int64(0); i < -exp.Value; i++ {
				num *= base.Denominator
				denom *= base.Numerator
			}
			if denom < 0 {
				num = -num
				denom = -denom
			}
			if denom == 0 {
				return &obj.Error{Message: "expt: division by zero"}
			}
			gcd := Gcd(num, denom)
			return &obj.Rational{Numerator: num / gcd, Denominator: denom / gcd}
		case *obj.Rational:
			// Rational to rational power - convert to real
			baseVal := float64(base.Numerator) / float64(base.Denominator)
			expVal := float64(exp.Numerator) / float64(exp.Denominator)
			return &obj.Real{Value: math.Pow(baseVal, expVal)}
		case *obj.Real:
			// Rational to real power
			baseVal := float64(base.Numerator) / float64(base.Denominator)
			return &obj.Real{Value: math.Pow(baseVal, exp.Value)}
		default:
			return &obj.Error{Message: fmt.Sprintf("expt expects number as second argument, got %s", exponent.Type())}
		}
	case *obj.Real:
		switch exp := exponent.(type) {
		case *obj.Integer:
			// Real to integer power
			return &obj.Real{Value: math.Pow(base.Value, float64(exp.Value))}
		case *obj.Rational:
			// Real to rational power
			expVal := float64(exp.Numerator) / float64(exp.Denominator)
			return &obj.Real{Value: math.Pow(base.Value, expVal)}
		case *obj.Real:
			// Real to real power
			return &obj.Real{Value: math.Pow(base.Value, exp.Value)}
		default:
			return &obj.Error{Message: fmt.Sprintf("expt expects number as second argument, got %s", exponent.Type())}
		}
	default:
		return &obj.Error{Message: fmt.Sprintf("expt expects number as first argument, got %s", base.Type())}
	}
}

// Apply function - applies a procedure to a list of arguments
func Apply(args []obj.Object, env obj.Environment) obj.Object {
	// Apply requires at least 2 arguments: procedure and at least one list
	if len(args) < 2 {
		return &obj.Error{Message: "apply requires at least 2 arguments"}
	}

	// The first argument must be a procedure
	proc := args[0]
	if proc.Type() != obj.BUILTIN_OBJ && proc.Type() != obj.PROCEDURE_OBJ {
		return &obj.Error{Message: "apply: first argument must be a procedure"}
	}

	// All arguments except the last one are regular arguments
	regularArgs := args[1 : len(args)-1]

	// The last argument must be a list
	lastArg, ok := args[len(args)-1].(*obj.List)
	if !ok {
		return &obj.Error{Message: "apply: last argument must be a list"}
	}

	// Combine regular arguments with the elements of the last list argument
	allArgs := append(regularArgs, lastArg.ToSlice()...)

	// Apply the procedure to all arguments
	switch p := proc.(type) {
	case *obj.Builtin:
		return p.Fn(allArgs, env)
	case *obj.Procedure:
		// Create AST expressions that evaluate to the given objects
		exprs := make([]ast.Expression, len(allArgs))
		for i, arg := range allArgs {
			// Create appropriate AST expressions based on the object type
			switch a := arg.(type) {
			case *obj.Integer:
				exprs[i] = &ast.IntegerLiteral{Value: a.Value}
			case *obj.Boolean:
				exprs[i] = &ast.BooleanLiteral{Value: a.Value}
			case *obj.String:
				exprs[i] = &ast.StringLiteral{Value: a.Value}
			default:
				// For other types, we need to create a quoted expression
				// This is a simplified approach - in a full implementation,
				// we would need to properly convert objects back to AST expressions
				exprs[i] = &ast.IntegerLiteral{Value: 0} // fallback
			}
		}
		return applyProcedure(p, exprs, env)
	default:
		return &obj.Error{Message: "apply: unknown procedure type"}
	}
}

// Map function - applies a procedure to each element of one or more lists
func Map(args []obj.Object, env obj.Environment) obj.Object {
	// Map requires at least 2 arguments: procedure and at least one list
	if len(args) < 2 {
		return &obj.Error{Message: "map requires at least 2 arguments"}
	}

	// The first argument must be a procedure
	proc := args[0]
	if proc.Type() != obj.BUILTIN_OBJ && proc.Type() != obj.PROCEDURE_OBJ {
		return &obj.Error{Message: "map: first argument must be a procedure"}
	}

	// The rest of the arguments must be lists
	lists := make([]*obj.List, len(args)-1)
	for i, arg := range args[1:] {
		list, ok := arg.(*obj.List)
		if !ok {
			return &obj.Error{Message: "map: all arguments after the first must be lists"}
		}
		lists[i] = list
	}

	// Check that all lists have the same length
	if len(lists) == 0 {
		return &obj.Error{Message: "map: at least one list argument required"}
	}

	listElements := lists[0].ToSlice()
	length := len(listElements)
	for i, list := range lists {
		otherElements := list.ToSlice()
		if len(otherElements) != length {
			return &obj.Error{Message: fmt.Sprintf("map: all lists must have the same length, list %d has %d elements, expected %d", i+1, len(otherElements), length)}
		}
	}

	// Apply the procedure to each set of elements
	resultElements := make([]obj.Object, length)
	for i := 0; i < length; i++ {
		// Create arguments by taking the i-th element from each list
		procArgs := make([]obj.Object, len(lists))
		for j, list := range lists {
			elements := list.ToSlice()
			procArgs[j] = elements[i]
		}

		// Apply the procedure to these arguments
		var result obj.Object
		switch p := proc.(type) {
		case *obj.Builtin:
			result = p.Fn(procArgs, env)
		case *obj.Procedure:
			// Create AST expressions that evaluate to the given objects
			exprs := make([]ast.Expression, len(procArgs))
			for k, arg := range procArgs {
				// Create appropriate AST expressions based on the object type
				switch a := arg.(type) {
				case *obj.Integer:
					exprs[k] = &ast.IntegerLiteral{Value: a.Value}
				case *obj.Boolean:
					exprs[k] = &ast.BooleanLiteral{Value: a.Value}
				case *obj.String:
					exprs[k] = &ast.StringLiteral{Value: a.Value}
				default:
					// For other types, we need to create a quoted expression
					// This is a simplified approach - in a full implementation,
					// we would need to properly convert objects back to AST expressions
					exprs[k] = &ast.IntegerLiteral{Value: 0} // fallback
				}
			}
			result = applyProcedure(p, exprs, env)
		default:
			return &obj.Error{Message: "map: unknown procedure type"}
		}

		if isError(result) {
			return result
		}
		resultElements[i] = result
	}

	// Create a proper list from the result elements
	if len(resultElements) == 0 {
		// Empty list
		return &obj.List{}
	}
	
	// Build the list from right to left
	resultList := &obj.List{Car: resultElements[len(resultElements)-1], Cdr: &obj.List{}}
	
	// Build the rest of the list from right to left
	for i := len(resultElements) - 2; i >= 0; i-- {
		resultList = &obj.List{Car: resultElements[i], Cdr: resultList}
	}
	
	return resultList
}

// Char function - checks if an object is a character
func Char(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 1 {
		return &obj.Error{Message: "char? requires exactly 1 argument"}
	}

	// Return #t if the argument is a character, #f otherwise
	if args[0].Type() == obj.CHARACTER_OBJ {
		return &obj.Boolean{Value: true}
	}
	return &obj.Boolean{Value: false}
}

// CharEqual function - checks if two characters are equal
func CharEqual(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 2 {
		return &obj.Error{Message: "char=? requires exactly 2 arguments"}
	}

	// Both arguments must be characters
	char1, ok1 := args[0].(*obj.Character)
	char2, ok2 := args[1].(*obj.Character)
	if !ok1 || !ok2 {
		return &obj.Error{Message: "char=? arguments must be characters"}
	}

	// Return #t if the characters are equal, #f otherwise
	return &obj.Boolean{Value: char1.Value == char2.Value}
}

// CharLess function - checks if the first character is less than the second
func CharLess(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 2 {
		return &obj.Error{Message: "char<? requires exactly 2 arguments"}
	}

	// Both arguments must be characters
	char1, ok1 := args[0].(*obj.Character)
	char2, ok2 := args[1].(*obj.Character)
	if !ok1 || !ok2 {
		return &obj.Error{Message: "char<? arguments must be characters"}
	}

	// Return #t if the first character is less than the second, #f otherwise
	return &obj.Boolean{Value: char1.Value < char2.Value}
}

// CharGreater function - checks if the first character is greater than the second
func CharGreater(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 2 {
		return &obj.Error{Message: "char>? requires exactly 2 arguments"}
	}

	// Both arguments must be characters
	char1, ok1 := args[0].(*obj.Character)
	char2, ok2 := args[1].(*obj.Character)
	if !ok1 || !ok2 {
		return &obj.Error{Message: "char>? arguments must be characters"}
	}

	// Return #t if the first character is greater than the second, #f otherwise
	return &obj.Boolean{Value: char1.Value > char2.Value}
}

// CharLessEqual function - checks if the first character is less than or equal to the second
func CharLessEqual(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 2 {
		return &obj.Error{Message: "char<=? requires exactly 2 arguments"}
	}

	// Both arguments must be characters
	char1, ok1 := args[0].(*obj.Character)
	char2, ok2 := args[1].(*obj.Character)
	if !ok1 || !ok2 {
		return &obj.Error{Message: "char<=? arguments must be characters"}
	}

	// Return #t if the first character is less than or equal to the second, #f otherwise
	return &obj.Boolean{Value: char1.Value <= char2.Value}
}

// CharGreaterEqual function - checks if the first character is greater than or equal to the second
func CharGreaterEqual(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 2 {
		return &obj.Error{Message: "char>=? requires exactly 2 arguments"}
	}

	// Both arguments must be characters
	char1, ok1 := args[0].(*obj.Character)
	char2, ok2 := args[1].(*obj.Character)
	if !ok1 || !ok2 {
		return &obj.Error{Message: "char>=? arguments must be characters"}
	}

	// Return #t if the first character is greater than or equal to the second, #f otherwise
	return &obj.Boolean{Value: char1.Value >= char2.Value}
}

// SetCar function - modifies the car of a pair
func SetCar(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 2 {
		return &obj.Error{Message: "set-car! requires exactly 2 arguments"}
	}

	// First argument must be a list (pair)
	pair, ok := args[0].(*obj.List)
	if !ok {
		return &obj.Error{Message: "set-car!: first argument must be a pair"}
	}

	// In our implementation, all lists have both Car and Cdr
	// Set the Car field to the new value
	pair.Car = args[1]
	
	return &obj.Void{}
}

// SetCdr function - modifies the cdr of a pair
func SetCdr(args []obj.Object, env obj.Environment) obj.Object {
	if len(args) != 2 {
		return &obj.Error{Message: "set-cdr! requires exactly 2 arguments"}
	}

	// First argument must be a list (pair)
	pair, ok := args[0].(*obj.List)
	if !ok {
		return &obj.Error{Message: "set-cdr!: first argument must be a pair"}
	}

	// In our implementation, all lists have both Car and Cdr
	// Set the Cdr field to the new value
	pair.Cdr = args[1]
	
	return &obj.Void{}
}
